// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraReplaySubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "Engine/DemoNetDriver.h"
#include "Internationalization/Text.h"
#include "Misc/DateTime.h"
#include "CommonUISettings.h"
#include "ICommonUIModule.h"
#include "LyraLogChannels.h"
#include "Player/LyraLocalPlayer.h"
#include "Settings/LyraSettingsLocal.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraReplaySubsystem)

UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Platform_Trait_ReplaySupport, "Platform.Trait.ReplaySupport");

ULyraReplaySubsystem::ULyraReplaySubsystem()
{
}

/**
 * 检查平台是否支持回放功能
 */
bool ULyraReplaySubsystem::DoesPlatformSupportReplays()
{
	if (ICommonUIModule::GetSettings().GetPlatformTraits().HasTag(GetPlatformSupportTraitTag())) // 如果平台特性包含回放支持标签
	{
		return true; // 返回true
	}
	return false; // 返回false
}

/**
 * 获取平台支持的特性标签
 */
FGameplayTag ULyraReplaySubsystem::GetPlatformSupportTraitTag()
{
	return TAG_Platform_Trait_ReplaySupport.GetTag(); // 返回平台特性标签
}

/**
 * 播放回放
 */
void ULyraReplaySubsystem::PlayReplay(ULyraReplayListEntry* Replay)
{
	if (Replay != nullptr) // 如果回放有效
	{
		FString DemoName = Replay->StreamInfo.Name; // 获取演示名称
		GetGameInstance()->PlayReplay(DemoName); // 播放回放
	}
}

/**
 * 录制客户端回放
 */
void ULyraReplaySubsystem::RecordClientReplay(APlayerController* PlayerController)
{
	if (ensure(DoesPlatformSupportReplays() && PlayerController)) // 确保平台支持回放且玩家控制器有效
	{
		FText FriendlyNameText = FText::Format(NSLOCTEXT("Lyra", "LyraReplayName_Format", "Client Replay {0}"), FText::AsDateTime(FDateTime::UtcNow(), EDateTimeStyle::Short, EDateTimeStyle::Short)); // 创建友好名称
		GetGameInstance()->StartRecordingReplay(FString(), FriendlyNameText.ToString()); // 开始录制回放

		if (ULyraLocalPlayer* LyraLocalPlayer = Cast<ULyraLocalPlayer>(PlayerController->GetLocalPlayer())) // 如果获取到本地玩家
		{
			// 开始清理现有的已保存流
			int32 NumToKeep = LyraLocalPlayer->GetLocalSettings()->GetNumberOfReplaysToKeep(); // 获取要保留的回放数量
			CleanupLocalReplays(LyraLocalPlayer, NumToKeep); // 清理本地回放
		}
	}
}

/**
 * 清理本地回放
 */
void ULyraReplaySubsystem::CleanupLocalReplays(ULocalPlayer* LocalPlayer, int32 NumReplaysToKeep)
{
	// TODO 这仅使用通用文件流器进行过测试，可能无法完全与保存游戏流器配合工作
	// 这每次只处理一个删除操作，并在出现错误或低于NumReplaysToKeep时循环
	// 这样做是因为每个删除操作可能涉及使回放列表无效的服务器或保存游戏查询
	if (LocalPlayer != nullptr && LocalPlayerDeletingReplays == nullptr && NumReplaysToKeep != 0) // 如果本地玩家有效且没有正在删除回放的本地玩家且保留数量不为0
	{
		LocalPlayerDeletingReplays = LocalPlayer; // 设置正在删除回放的本地玩家
		DeletingReplaysNumberToKeep = NumReplaysToKeep; // 设置要保留的数量

		CurrentReplayStreamer = FNetworkReplayStreaming::Get().GetFactory().CreateReplayStreamer(); // 创建当前回放流器
		if (CurrentReplayStreamer.IsValid()) // 如果当前回放流器有效
		{
			// 使用默认版本来获取旧版本的回放
			FNetworkReplayVersion EnumerateStreamsVersion; // 枚举流版本

			CurrentReplayStreamer->EnumerateStreams(EnumerateStreamsVersion, LocalPlayer->GetPlatformUserIndex(), FString(), TArray<FString>(), FEnumerateStreamsCallback::CreateUObject(this, &ThisClass::OnEnumerateStreamsCompleteForDelete)); // 枚举流
		}
	}
}

/**
 * 当删除操作的枚举流完成时调用
 */
void ULyraReplaySubsystem::OnEnumerateStreamsCompleteForDelete(const FEnumerateStreamsResult& Result)
{
	if (!CurrentReplayStreamer.IsValid() || !IsValid(LocalPlayerDeletingReplays)) // 如果当前回放流器无效或正在删除回放的本地玩家无效
	{
		// 上下文丢失，不执行任何操作
		return;
	}

	TArray<FNetworkReplayStreamInfo> StreamsToDelete; // 要删除的流数组
	for (const FNetworkReplayStreamInfo& StreamInfo : Result.FoundStreams) // 遍历找到的流
	{
		// 永不删除应保留的流
		if (!StreamInfo.bShouldKeep) // 如果流不应保留
		{
			StreamsToDelete.Add(StreamInfo); // 添加到要删除的数组
		}
	}

	// 按日期排序
	Algo::SortBy(StreamsToDelete, [](const FNetworkReplayStreamInfo& Data) { return Data.Timestamp.GetTicks(); }, TGreater<>()); // 使用时间戳排序

	if (UDemoNetDriver* DemoDriver = GetDemoDriver()) // 如果获取到演示网络驱动器
	{
		if (DemoDriver->IsRecording()) // 如果正在录制
		{
			// 如果我们正在录制，活动流可能会也可能不会出现在查询中，这会影响保留计数
			// 如果活动流在结果中缺失，则添加一个假的活动流
			if (StreamsToDelete.Num() > 0 && !StreamsToDelete[0].bIsLive) // 如果要删除的流数量大于0且第一个流不是直播
			{
				StreamsToDelete.Insert(FNetworkReplayStreamInfo(), 0); // 在开头插入一个空的流信息
			}
		}
	}

	if (StreamsToDelete.Num() > DeletingReplaysNumberToKeep) // 如果要删除的流数量大于要保留的数量
	{
		// 删除超过限制的第一个回放，如果成功，它将在下一次循环中不在列表中
		// 如果不成功，它将停止循环
		FString ReplayName = StreamsToDelete[DeletingReplaysNumberToKeep].Name; // 获取回放名称
		UE_LOG(LogLyra, Log, TEXT("LyraReplaySubsystem asked to delete replay %s"), *ReplayName); // 记录日志
		CurrentReplayStreamer->DeleteFinishedStream(ReplayName, LocalPlayerDeletingReplays->GetPlatformUserIndex(), FDeleteFinishedStreamCallback::CreateUObject(this, &ThisClass::OnDeleteReplay)); // 删除完成的流
	}
	else
	{
		// 我们低于限制，因此停止迭代
		CurrentReplayStreamer = nullptr; // 清空当前回放流器
		LocalPlayerDeletingReplays = nullptr; // 清空正在删除回放的本地玩家
		DeletingReplaysNumberToKeep = 0; // 重置要保留的数量
	}
}

/**
 * 当删除回放完成时调用
 */
void ULyraReplaySubsystem::OnDeleteReplay(const FDeleteFinishedStreamResult& DeleteResult)
{
	if (!CurrentReplayStreamer.IsValid() || !IsValid(LocalPlayerDeletingReplays)) // 如果当前回放流器无效或正在删除回放的本地玩家无效
	{
		// 上下文丢失，不执行任何操作
		return;
	}

	if (DeleteResult.WasSuccessful()) // 如果删除成功
	{
		// 再次枚举列表以查看我们是否已低于限制
		FNetworkReplayVersion EnumerateStreamsVersion; // 枚举流版本

		CurrentReplayStreamer->EnumerateStreams(EnumerateStreamsVersion, LocalPlayerDeletingReplays->GetPlatformUserIndex(), FString(), TArray<FString>(), FEnumerateStreamsCallback::CreateUObject(this, &ThisClass::OnEnumerateStreamsCompleteForDelete)); // 枚举流
	}
	else
	{
		// 失败，停止尝试删除任何其他内容
		// TODO 正确集成平台特定的错误报告
		UE_LOG(LogLyra, Warning, TEXT("Failed to delete replay with error %d!"), (int32)DeleteResult.Result); // 记录警告日志

		CurrentReplayStreamer = nullptr; // 清空当前回放流器
		LocalPlayerDeletingReplays = nullptr; // 清空正在删除回放的本地玩家
		DeletingReplaysNumberToKeep = 0; // 重置要保留的数量
	}
}

/**
 * 在活动回放中跳转
 */
void ULyraReplaySubsystem::SeekInActiveReplay(float TimeInSeconds)
{
	if (UDemoNetDriver* DemoDriver = GetDemoDriver()) // 如果获取到演示网络驱动器
	{
		DemoDriver->GotoTimeInSeconds(TimeInSeconds); // 跳转到指定时间
	}
}

/**
 * 获取回放长度（秒）
 */
float ULyraReplaySubsystem::GetReplayLengthInSeconds() const
{
	if (UDemoNetDriver* DemoDriver = GetDemoDriver()) // 如果获取到演示网络驱动器
	{
		return DemoDriver->GetDemoTotalTime(); // 返回演示总时间
	}
	return 0.0f; // 返回0
}

/**
 * 获取回放当前时间
 */
float ULyraReplaySubsystem::GetReplayCurrentTime() const
{
	if (UDemoNetDriver* DemoDriver = GetDemoDriver()) // 如果获取到演示网络驱动器
	{
		return DemoDriver->GetDemoCurrentTime(); // 返回演示当前时间
	}
	return 0.0f; // 返回0
}

/**
 * 获取演示网络驱动器
 */
UDemoNetDriver* ULyraReplaySubsystem::GetDemoDriver() const
{
	if (UWorld* World = GetGameInstance()->GetWorld()) // 如果获取到世界
	{
		return World->GetDemoNetDriver(); // 返回演示网络驱动器
	}
	return nullptr; // 返回空
}